--[[
	核心的操作redis和mysql的service
	用户数据存储在两处 redis，mysql， mysql是最终落地的数据，
	但如果redis中有数据，则再不会访问mysql
	只是整个流程，逻辑的修改，以及redis存储的修改
	底层data操作函数，还是用的原来的
]]
local filename = "opdata.lua"

local skynet = require "skynet"
local queue = require "skynet.queue"

local filelog = require "filelog"
local configdao = require "configdao"
local redisdao = require "redisdao"
local mysqldao = require "mysqldao"
local json = require "cjson.safe"
json.encode_sparse_array(true,1,1)

local tonumber = tonumber
local tostring = tostring

local tabletool = require "tabletool"
local timetool = require "timetool"
local dbstruct =  require "dbstruct"

local cs = queue()
local CMD = {}
local redisdb
local mysqldb

-- 按照 dbstruct 中的字段的数据类型，调整一下从redis， mysql中获得的数据
-- redis, mysql里面获得都是字符串
-- 数据库定义的只有字符串和整数两种类型，数据库只是存储
function format_result(result, table)
	for item,itemvalue in pairs(dbstruct[table]["fields"]) do
		if type(itemvalue) == "number" then
			result[item] = tonumber(result[item])
		end
	end
	return result
end

-- 分单行和多行表操作, 这里不用单独的某个数据
-- 首先从redis里面查询，如果没有再从mysql中查询，并把查询到的记录全部记录到 redis里面
-- 单行表，hash存储，键：table:rid, 
-- 多行表，redis里面落地成两个键值,
-- 主数据就是Hash， 键table:id, keyid 为key, 
-- table:rid:zset, 是一个sorted set 集合，按keyid是元素，create_time 来排序（zadd，zrange）

-- 单行表有个小技巧，默认查询没有，要插入一行新数据，这里返回行数2，调用者获得后，知道2 ，说明是新插入的数据
-- 可以用在新用户注册的时候，标明是新玩家的行为

-- 多行表的问题就是如果真的没有值, 这里有个小技巧，加入一个 zadd 0 0,作为没有的填充，那么hash里的条数，就是zcard() - 1
-- 使用 zrange table:rid:zset 1 -1 WITHSCORES 返回除0外的所有keyid,zrem,删除， zscore 查询member值

-- other_role : 这里有个获取 其他rid 信息的问题，
-- 如果是错误的rid，则在数据库中也查询不到的情况下，不要去创建新的数据
-- 大部分情况默认情况下都不传这个 other_role，只有在查看其他玩家信息的时候
-- other_role 也起了控制，不插入的作用。所以当某些时候，是第三方来selecet  rs_xxx 的时候，记得一定要设置 other_role
local function select_rk(rid, table, other_role)
	local result, recordcount, zsetkey, hashkey
	local result_data = {}
	local record = {} -- keyid 为主键的每条记录,单行表就是rid
	local tmprec = {}
	local condition = {}
	-- print("service_address : " , skynet.address(skynet.self())) -- 做测试用，看是否分片了
	zsetkey = table ..":"..rid .. ":zset"
	hashkey = table ..":"..rid
	local insertnew = 1
	if table:sub(2,2) == 's' then
		-- 这个时候keyid，不需要关注了
		-- 先在redis中查找，是否有这个记录
		result, result_data = redisdao.query_data(redisdb, "hgetall", hashkey)
		if #result_data ~=0 then
			for i=1, (#result_data)/2 do
				tmprec[result_data[2*i-1]] = result_data[2*i]
			end
			record[rid] = format_result(tmprec, table)
			return {1, record}
		else
			-- 没有，就去mysql中查找该条
			result, result_data = mysqldao.select(mysqldb, table, "where rid =" .. rid)
			if #result_data == 0 then
				-- 数据库中也没有，则首先默认值写入数据库
				-- 如果是查看别的rid的信息，数据库也没有，说明是错误rid，不必要做任何插入
				if not other_role then
					local tmpdata ={}
					result_data = tabletool.deepcopy(dbstruct[table]["fields"])
					result_data.rid = rid
					if result_data.rolename and result_data.rolename == "_" then
						result_data.rolename = "_" .. rid
					end
					result_data.create_time = timetool.get_time()
					result, tmpdata = mysqldao.sync_insert(mysqldb, table, _, result_data)
					if tmpdata.err then
						-- 插入失败
						filelog.sys_error(filename.." CMD:select_rk@" ..tmpdata.err .. "|" ..table ..  "|" .. rid)
						return {0, {}}
					end
					insertnew = 2
				else
					return {0, {}}
				end
			else
				result_data = result_data[1]
			end
			-- mysql中有，redis中无，则把数据写入redis，需要分解成 hset的形式
			for k,v in pairs(result_data) do
				redisdao.query_data(redisdb, "hset", hashkey, k, v)
			end
			record[rid] = format_result(result_data, table)
			return {insertnew, record}
		end
	else -- 处理多行表
		-- 首先看table:rid:zset 是否存在，如果存在说明是redis中有数据，则直接读取 table:rid(可能还是为空，没有多行数据)
		-- 如果reids中不存在，则去数据库中获取，全部写入到hash中
		result, recordcount = redisdao.query_data(redisdb, "zcard", zsetkey)
		if recordcount == 1 then
			return {0, {}} --说明只有 0 这个member，没有多行记录
		elseif recordcount > 1 then
			result, result_data = redisdao.query_data(redisdb, "hgetall", hashkey)
			local errornum = 0
			local decoderecode = nil
			for i=1, (#result_data)/2 do
				decoderecode = json.decode(result_data[2*i]) -- 这里可能会decode出错，用了cjson.safe 会返回nil, 要跳过这条记录
				if decoderecode then
					record[result_data[2*i-1]] = decoderecode
				else
					errornum = errornum + 1
				end
			end
			return {recordcount-1-errornum, record}
		else -- recordcount == 0
			-- 没有这个值，就去mysql中查找. zsetkey ，是必须要有的，标识这这个多行表数据在redis里面
			result, result_data = mysqldao.select(mysqldb, table, "where rid =" .. rid)
			redisdao.query_data(redisdb, "zadd", zsetkey, 0, 0)
			recordcount = #result_data
			if recordcount == 0  then
				return {0, {}}
			else
				for i,j in ipairs(result_data) do
					-- j 是一条记录
					j = format_result(j, table)
					redisdao.query_data(redisdb, "zadd", zsetkey, j.create_time, j.keyid)
					redisdao.query_data(redisdb, "hset", hashkey, j.keyid, json.encode(j))
					record[tostring(j.keyid)] = j -- 这个时候keyid 从数据库中取得的是 int， 但redis里面全是整形，所以这里直接设置成字符串了
				end
				return {recordcount, record}
			end
		end
	end
end 

function CMD.select_rk(rid, table, other_role)
	return cs(select_rk, rid, table, other_role)
end

-- 参数：$rid, $table, $keyid, $fields
-- 单行表是没有显式插入的。也没有删除的
local function insert_rk(rid, table, keyid, fields)
	local result, recordcount, zsetkey, hashkey
	local result_data = {}
	local record = {} -- keyid 为主键的每条记录,单行表就是rid
	local tmprec = {}
	local condition = {}
	if table:sub(2,2) == 's' then
		return {0, {}}
	end
	zsetkey = table ..":"..rid .. ":zset"
	hashkey = table ..":"..rid

	-- 首先获得默认值，然后把fields中的值覆盖上去
	result_data = tabletool.deepcopy(dbstruct[table]["fields"])
	for k,v in pairs(fields) do
		if result_data[k] then
			result_data[k] = v
		end
	end	
	result_data.rid = rid	-- 这两步是怕fields里面有rid， keyid字段
	result_data.keyid = keyid
	result_data.create_time = timetool.get_time()


	result, recordcount = redisdao.query_data(redisdb, "zcard", zsetkey)
	-- filelog.print("line173", result, recordcount)
	if recordcount == 0 then
		-- redis中没有记录，先从mysql中导入到redis中，因为mysql中就算是空，redis中也应该有个zsetkey zset{0,0}
		CMD.select_rk(rid, table)
	end
	-- 先写入redis，
	redisdao.query_data(redisdb, "zadd", zsetkey, result_data.create_time, keyid)
	redisdao.query_data(redisdb, "hset", hashkey, keyid, json.encode(result_data))
	-- 有的数据不写入DB，简化处理，比如战绩
	-- if dbstruct[table].storage > 1 then
	if skynet.getenv("runtime") ~= "r" or dbstruct[table].storage > 1 then
		result, result_data = mysqldao.sync_insert(mysqldb, table, _, result_data)
		if result_data.err then
			-- 插入失败,删除redis数据
            redisdao.query_data(redisdb, "del", zsetkey)
	        redisdao.query_data(redisdb, "del", hashkey)
			filelog.sys_error(filename.." CMD:insert_rk@" ..result_data.err .. "|" ..table ..  "|" .. rid.. "|" .. (keyid or "") .. "|" .. json.encode(fields))
            return {0, false}
		end
	end
	return {1, {}}
end

function CMD.insert_rk(rid, table, keyid, fields)
	return cs(insert_rk, rid, table, keyid, fields)
end


local function delete_rk(rid, table, keyid)
	local result, recordcount, zsetkey, hashkey
	local result_data = {}
	local record = {} -- keyid 为主键的每条记录,单行表就是rid
	local tmprec = {}
	local condition = {}
	if table:sub(2,2) == 's' then
		return {0, {}}
	end
	zsetkey = table ..":"..rid .. ":zset"
	hashkey = table ..":"..rid
	result, recordcount = redisdao.query_data(redisdb, "zcard", zsetkey)
	-- redis 里面存在，先删除redis
	if recordcount ~= 0 then
		redisdao.query_data(redisdb, "zrem", zsetkey, keyid)
		redisdao.query_data(redisdb, "hdel", hashkey, keyid)
	end
	condition["rid"] = rid
	condition["keyid"] = keyid
	result, result_data = mysqldao.delete(mysqldb, table, condition)
	return {1, {}}
end

function CMD.delete_rk(rid, table, keyid)
	return cs(delete_rk, rid, table, keyid)
end

-- 这里注意keyid 要放到fields的后面， 是按照  uppdate teble se xxx where keyid 顺序来的
-- 单行的时候， keyid可以省略
local function update_rk(rid, table, keyid, fields)
	local result, recordcount, zsetkey, hashkey
	local result_data = {}
	local record = {} -- keyid 为主键的每条记录,单行表就是rid
	local tmprec = {}
	local condition = {}
	zsetkey = table ..":"..rid .. ":zset"
	hashkey = table ..":"..rid
	local cleanfields = {}
	for k,v in pairs(fields) do
		if k ~= "rid" and k ~= "keyid" then
			cleanfields[k] = v
		end
	end
	if table:sub(2,2) == 's' then
		-- 先看数据是否在redis里面。hset修改redis里的数据，再修改mysql
		result, result_data = redisdao.query_data(redisdb, "hgetall", hashkey)
		if result_data and #result_data ~= 0 then
			for k,v in pairs(cleanfields) do
				redisdao.query_data(redisdb, "hset", hashkey, k, v)
			end
		end
		condition["rid"] = rid
		result, result_data = mysqldao.sync_update(mysqldb, table, condition, cleanfields)
		if result_data.err then
			-- 更改失败,回滚数据,直接删除，以数据库数据为准
            redisdao.query_data(redisdb, "del", hashkey)
			filelog.sys_error(filename.." CMD:update_rk@" ..result_data.err .. "|" ..table ..  "|" .. rid.. "|" .. (keyid or "") .. "|" .. json.encode(fields))
            return {0, false}
		end
		return {1, true}
	else
		-- 多行的话，需要从redis里面读取数据（json），decode，修改，encode，然后再hset进去
		-- 先通过zscore 读取这member的分数，如果是nil，说明这个不存在，
		result, result_data = redisdao.query_data(redisdb, "zscore", zsetkey, keyid)
		if result_data then
			result, result_data = redisdao.query_data(redisdb, "hget", hashkey, keyid)
			if result_data then
				record = json.decode(result_data) -- 这里可能decode出nil
				if record then
					for k,v in pairs(cleanfields) do
						record[k] = v
					end
					redisdao.query_data(redisdb, "hset", hashkey, keyid, json.encode(record))
				end
			end
		end
		condition["rid"] = rid
		condition["keyid"] = keyid
		result, result_data = mysqldao.sync_update(mysqldb, table, condition, cleanfields)
		if result_data.err then
            --更改失败，回滚数据，多行表直接删除redis数据
            redisdao.query_data(redisdb, "del", zsetkey)
            redisdao.query_data(redisdb, "del", hashkey)
			filelog.sys_error(filename.." CMD:update_rk@" ..result_data.err .. "|" ..table ..  "|" .. rid.. "|" .. (keyid or "") .. "|" .. json.encode(fields))
            return {0, false}
		end
		return {1, true}
	end
end

function CMD.update_rk(rid, table, keyid, fields)
	return cs(update_rk, rid, table, keyid, fields)
end

function CMD.mysqlquery(sqlstr)
	local result, result_data = mysqldao.query(mysqldb, sqlstr)
	return {result, result_data}
end

function CMD.init()
	-- 每个操作service，都有单独创建自己到redis，mysql的链接
	local dataredis = configdao.get_cfgsvrs_key("dataredis")
	if dataredis ~= nil then
		redisdb = skynet.newservice("redisdb")
		skynet.call(redisdb, "lua", "init", dataredis)
	end
	local datamysql = configdao.get_cfgsvrs_key("datamysql")
	if datamysql ~= nil then
		mysqldb = skynet.newservice("mysqldb")
		skynet.call(mysqldb, "lua", "init", datamysql)
	end
end

skynet.start(function()
	skynet.dispatch("lua", function(session, address, cmd, ...)
		local f = CMD[cmd]
		if f then
			skynet.ret(skynet.pack(f(...)))
		else
			error(string.format("Unknown cmd %s", tostring(cmd)))
		end
	end)
end)